﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Collections.ObjectModel;
using System.ServiceModel.Activation;
using System.Threading;
using System.Xml;
using System.IO;
using Moq;
using System.Diagnostics;

namespace Airport
{
    
    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single)]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class AirportService : IAirportService
    {
        // Set this to true to use predefined test data
        private bool _useTestData = true;
        // Test data structures
        private List<Route> testRoutes = new List<Route>();

        public static Dictionary<IAirportCallback, IAirportCallback> _clients =
            new Dictionary<IAirportCallback, IAirportCallback>();

        public static Dictionary<IAirportCallback, IAirportCallback> _planeUpdateClients =
           new Dictionary<IAirportCallback, IAirportCallback>();

        // Airport environment data structures
        private Map _airportMap;
        private List<Plane> _activePlanes;
        private List<Flight> _activeFlights;

        // This allows for mocking
        private OperationContext _operationContext;

        public AirportService()
            : this(OperationContext.Current)
        {
        }

        public AirportService(OperationContext currentOperationContext)
        {
            _activePlanes = new List<Plane>();
            _activeFlights = new List<Flight>();

            _operationContext = currentOperationContext;

            if (_airportMap == null)
            {
                // Load airport map from XML file
                _airportMap = LoadAirportMapFromFile(Path.GetFullPath(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "airport.xml")));                
            }

            if (_useTestData)
            {
                SetupInitialConditions();
            }

            /*
            new Thread(new ThreadStart(pumpUpdates))
            {
                IsBackground = true
            }
            .Start();
            */ 
        }

        private void SetupInitialConditions()
        {   
            // Set this to the number of test planes to create
            int numTestPlanes = 2;

            Plane[] testPlanes = new Plane[numTestPlanes];
            int[] testHeadings = new int[numTestPlanes];
            Node[] initialPlaneLocations = new Node[numTestPlanes];
            Flight[] testFlights = new Flight[numTestPlanes];
            string[] testFlightNames = new string[numTestPlanes];

            // Plane Headings
            testHeadings[0] = 220;
            testHeadings[1] = 220;

            // Initial Plane Locations
            initialPlaneLocations[0] = new Node(-1, 512, 271); // Beginning of Taxiway C10
            initialPlaneLocations[1] = new Node(-1, 684, 250); // Beginning of Taxiway C6          
           
            // Airline Codes
            testFlightNames[0] = "SWA222";
            testFlightNames[1] = "VX89";

            // Create a test Route
            Route testRoute = new Route();
            List<RouteNode> testRouteNodes = new List<RouteNode>();
            /*testRouteNodes.Add(new RouteNode(0, 512, 271));
            testRouteNodes.Add(new RouteNode(1, 516, 326));
            testRouteNodes.Add(new RouteNode(2, 519, 345));
            testRouteNodes.Add(new RouteNode(3, 564, 339));
            testRouteNodes.Add(new RouteNode(4, 607, 332));
            testRouteNodes.Add(new RouteNode(5, 620, 332));
            testRouteNodes.Add(new RouteNode(6, 651, 327));
            testRouteNodes.Add(new RouteNode(7, 679, 327));
            testRouteNodes.Add(new RouteNode(8, 693, 323));
            testRouteNodes.Add(new RouteNode(9, 706, 322));
            testRouteNodes.Add(new RouteNode(10, 743, 316));
            testRouteNodes.Add(new RouteNode(11, 768, 314));
            testRouteNodes.Add(new RouteNode(12, 796, 311));
            testRouteNodes.Add(new RouteNode(13, 836, 307));
            testRouteNodes.Add(new RouteNode(14, 868, 304));
            testRouteNodes.Add(new RouteNode(15, 894, 300));
            testRouteNodes.Add(new RouteNode(16, 952, 292));
            testRouteNodes.Add(new RouteNode(17, 1005, 285));
            testRouteNodes.Add(new RouteNode(18, 1012, 306));
            */
            // 24L - Plane 0
            //testRouteNodes.Add(new RouteNode(0, 512, 271));
            testRouteNodes.Add(new RouteNode(0, 516, 326));
            testRouteNodes.Add(new RouteNode(1, 492, 329));
            testRouteNodes.Add(new RouteNode(2, 481, 331));
            testRouteNodes.Add(new RouteNode(3, 475, 287));
            testRouteNodes.Add(new RouteNode(4, 458, 145));
            testRoute.RouteNodes = testRouteNodes;
            testRoutes.Add(testRoute);            

            // 24R - Plane 0
            testRoute = new Route();
            testRouteNodes = new List<RouteNode>();
            //testRouteNodes.Add(new RouteNode(0, 512, 271));
            testRouteNodes.Add(new RouteNode(0, 516, 326));
            testRouteNodes.Add(new RouteNode(1, 492, 329));
            testRouteNodes.Add(new RouteNode(2, 481, 331));
            testRouteNodes.Add(new RouteNode(3, 475, 287));
            testRouteNodes.Add(new RouteNode(4, 458, 145));
            testRoute.RouteNodes = testRouteNodes;
            testRoutes.Add(testRoute);            

            // 25L - Plane 0
            testRoute = new Route();
            testRouteNodes = new List<RouteNode>();
            //testRouteNodes.Add(new RouteNode(0, 512, 271));
            testRouteNodes.Add(new RouteNode(0, 516, 329));
            testRouteNodes.Add(new RouteNode(1, 519, 345));
            testRouteNodes.Add(new RouteNode(2, 564, 339));
            testRouteNodes.Add(new RouteNode(3, 952, 292));
            testRouteNodes.Add(new RouteNode(4, 955, 312));
            testRoute.RouteNodes = testRouteNodes;
            testRoutes.Add(testRoute);            

            // 25R - Plane 0
            testRoute = new Route();
            testRouteNodes = new List<RouteNode>();
            //testRouteNodes.Add(new RouteNode(0, 512, 271));
            testRouteNodes.Add(new RouteNode(0, 516, 329));
            testRouteNodes.Add(new RouteNode(1, 519, 345));
            testRouteNodes.Add(new RouteNode(2, 564, 339));
            testRouteNodes.Add(new RouteNode(3, 1005, 285));
            testRouteNodes.Add(new RouteNode(4, 1012, 306));
            testRoute.RouteNodes = testRouteNodes;
            testRoutes.Add(testRoute);

            // 24L - Plane 1
            testRoute = new Route();
            testRouteNodes = new List<RouteNode>();
            testRouteNodes.Add(new RouteNode(0, 692, 306));            
            testRouteNodes.Add(new RouteNode(1, 492, 329));
            testRouteNodes.Add(new RouteNode(2, 481, 331));
            testRouteNodes.Add(new RouteNode(3, 475, 287));
            testRouteNodes.Add(new RouteNode(4, 458, 145));
            testRoute.RouteNodes = testRouteNodes;
            testRoutes.Add(testRoute);

            // 24R - Plane 1
            testRoute = new Route();
            testRouteNodes = new List<RouteNode>();
            testRouteNodes.Add(new RouteNode(0, 692, 306));
            testRouteNodes.Add(new RouteNode(1, 492, 329));
            testRouteNodes.Add(new RouteNode(2, 481, 331));
            testRouteNodes.Add(new RouteNode(3, 475, 287));
            testRouteNodes.Add(new RouteNode(4, 458, 145));
            testRoute.RouteNodes = testRouteNodes;
            testRoutes.Add(testRoute);

            // 25L - Plane 1
            testRoute = new Route();
            testRouteNodes = new List<RouteNode>();
            testRouteNodes.Add(new RouteNode(0, 692, 306));
            testRouteNodes.Add(new RouteNode(1, 693, 323));
            testRouteNodes.Add(new RouteNode(2, 706, 322));
            testRouteNodes.Add(new RouteNode(3, 952, 292));
            testRouteNodes.Add(new RouteNode(4, 955, 312));
            testRoute.RouteNodes = testRouteNodes;
            testRoutes.Add(testRoute);

            // 25R - Plane 1
            testRoute = new Route();
            testRouteNodes = new List<RouteNode>();
            testRouteNodes.Add(new RouteNode(0, 692, 306));
            testRouteNodes.Add(new RouteNode(1, 693, 323));
            testRouteNodes.Add(new RouteNode(2, 706, 322));
            testRouteNodes.Add(new RouteNode(3, 1005, 285));
            testRouteNodes.Add(new RouteNode(4, 1012, 306));
            testRoute.RouteNodes = testRouteNodes;
            testRoutes.Add(testRoute);

            for (int i = 0; i < numTestPlanes; i++)
            {
                testFlights[i] = new Flight();
                testFlights[i].FlightId = i;
                testFlights[i].FlightName = testFlightNames[i];

                testPlanes[i] = new Plane();
                testPlanes[i].PlaneId = i;
                testPlanes[i].Heading = testHeadings[i];
                testPlanes[i].Location = initialPlaneLocations[i];                
                testPlanes[i].PlaneFlight = testFlights[i];

                _activePlanes.Add(testPlanes[i]);
                _activeFlights.Add(testFlights[i]);
            }
        }

        public List<Plane> ActivePlanes
        {
            get
            {
                return _activePlanes;
            }

            set
            {
                _activePlanes = value;
            }
        }

        public List<Flight> ActiveFlights
        {
            get
            {
                return _activeFlights;
            }

            set
            {
                _activeFlights = value;
            }
        }

        public void RegisterForFlightUpdates()
        {
            IAirportCallback c = OperationContext.Current.GetCallbackChannel<IAirportCallback>();            
            
            // TESTING
            //Debug.WriteLine(c.GetType());

            if (!_clients.ContainsKey(c))
            {
                lock (_clients)
                {
                    _clients.Add(c, c);
                }
            }
        }

        public void UnregisterForFlightUpdates()
        {
            IAirportCallback c = OperationContext.Current.GetCallbackChannel<IAirportCallback>();
            if (_clients.ContainsKey(c))
            {
                lock (_clients)
                {
                    _clients.Remove(c);
                }
            }
        }

        public void RegisterForPlaneUpdates()
        {
            IAirportCallback c = OperationContext.Current.GetCallbackChannel<IAirportCallback>();
            if (!_planeUpdateClients.ContainsKey(c))
            {
                lock (_planeUpdateClients)
                {
                    _planeUpdateClients.Add(c, c);
                }
            }
        }

        // Clients call this function at startup to get the airport layout
        public void GetAirportLayout()
        {
            IAirportCallback c = OperationContext.Current.GetCallbackChannel<IAirportCallback>();

            if (_airportMap == null)
            {
                // Load airport map from XML file
                _airportMap = LoadAirportMapFromFile(Path.GetFullPath("airport.xml"));
            }
            
            c.IncomingAirportLayout(new Map((Map)_airportMap));
        }

        public Map GetAirportLayoutReqestReply()
        {
            if (_airportMap == null)
            {
                // Load airport map from XML file
                _airportMap = LoadAirportMapFromFile(Path.GetFullPath("airport.xml"));
            }

            return new Map((Map)_airportMap);
        }

        public void GetCurrentPlaneList()
        {
            GetCurrentPlaneList(OperationContext.Current.GetCallbackChannel<IAirportCallback>());
        }

        public void GetCurrentPlaneList(IAirportCallback c)
        {
            try
            {
                c.SendCurrentPlaneList(_activePlanes);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("AirportService: {0}", ex.Message);
            }
        }

        public void GetSuggestion(Plane updatedPlane, Node destinationNode)
        {
            GetSuggestion(OperationContext.Current.GetCallbackChannel<IAirportCallback>(), updatedPlane, destinationNode);
        }

        // For unit testing
        public void GetSuggestion(IAirportCallback c, Plane updatedPlane, Node destinationNode)
        {
            try
            {
                //IAirportCallback c = OperationContext.Current.GetCallbackChannel<IAirportCallback>();
                //IAirportCallback c = _operationContext.GetCallbackChannel<IAirportCallback>();            

                // TODO Check that updatedFlight is in ActiveFlights

                // TODO Check that destinationNode is in the AirportMap
                /*
                Plane foundPlane = ActivePlanes.Find(
                    delegate(Plane plane)
                    {
                        return plane.PlaneId == updatedFlight.FlightPlane.PlaneId;
                    });

                foundPlane.DestinationNode = destinationNode;
                */
                // TODO Get suggested route from SuggestionEngine
                Suggestion pendingSuggestion = new Suggestion();

                #region TESTING
                /*
                // Create a test Route
                Route testRoute = new Route();
                List<RouteNode> testRouteNodes = new List<RouteNode>();
                testRouteNodes.Add(new RouteNode(0, 90, 91));
                testRouteNodes.Add(new RouteNode(1, 185, 79));
                testRouteNodes.Add(new RouteNode(2, 280, 67));
                testRouteNodes.Add(new RouteNode(3, 298, 65));
                testRouteNodes.Add(new RouteNode(4, 392, 52));
                testRouteNodes.Add(new RouteNode(5, 527, 36));
                testRoute.RouteNodes = testRouteNodes;
                */
                
                Node node24L = new Node();
                Node node24R = new Node();
                Node node25L = new Node();
                Node node25R = new Node();

                node24L.XPos = 624;
                node24L.YPos = 83;
                node24R.XPos = 618;
                node24R.YPos = 42;
                node25L.XPos = 959;
                node25L.YPos = 359;
                node25R.XPos = 1012;
                node25R.YPos = 306;

                // 24L
                if (destinationNode.Equals(node24L))
                {                    
                    pendingSuggestion.SuggestionRoute = testRoutes[0 + (updatedPlane.PlaneId) * 4];                   
                }                 
                // 24R
                else if (destinationNode.Equals(node24R))
                {
                    pendingSuggestion.SuggestionRoute = testRoutes[1 + (updatedPlane.PlaneId) * 4];
                }
                // 25L
                else if (destinationNode.Equals(node25L))
                {
                    pendingSuggestion.SuggestionRoute = testRoutes[2 + (updatedPlane.PlaneId) * 4];
                }
                // 25R
                else if (destinationNode.Equals(node25R))
                {
                    pendingSuggestion.SuggestionRoute = testRoutes[3 + (updatedPlane.PlaneId) * 4];
                }

                #endregion
                pendingSuggestion.SuggestionPlane = updatedPlane;
                //pendingSuggestion.SuggestionRoute = testRoutes[0];
                

                // Send suggested route to the client that requested it
                c.SendSuggestion(pendingSuggestion);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("AirportService: {0}", ex.Message);
            }
        }

        public void AcceptSuggestion(Suggestion acceptedSuggestion)
        {
            try
            {
                // TODO Check that suggestion is valid
                if (acceptedSuggestion == null)
                {
                    throw (new ArgumentNullException("acceptedSuggestion"));
                }

                if (acceptedSuggestion.SuggestionPlane == null)
                {
                    throw (new ArgumentException("Suggestion must have non-null SuggestionPlane property", "acceptedSuggestion"));
                }

                if (acceptedSuggestion.SuggestionRoute == null)
                {
                    throw (new ArgumentException("Suggestion must have non-null SuggestionRoute property", "acceptedSuggestion"));
                }
                
                Plane foundPlane = ActivePlanes.Find(
                    delegate(Plane plane)
                    {
                        //return plane.PlaneId == acceptedSuggestion.SuggestionPlane.PlaneId;
                        return plane.Equals(acceptedSuggestion.SuggestionPlane);
                    });
                                
                // Set plane's CurrentRoute to the Suggestion's Route
                foundPlane.CurrentRoute = acceptedSuggestion.SuggestionRoute;
                // Set plane's destination node to the Suggestion's DestinationNode
                foundPlane.DestinationNode = acceptedSuggestion.SuggestionPlane.DestinationNode;
                // Set plane's UpdateType to UpdateExistingPlane
                foundPlane.UpdateType = PlaneUpdateType.UpdateExistingPlane;

                // Fork a new thread and inform all clients about accepted Route 
                new Thread(new ThreadStart(
                    delegate()
                    {
                        lock (_planeUpdateClients)
                        {
                            if (_planeUpdateClients.Count > 0)
                            {
                                foreach (IAirportCallback client in _planeUpdateClients.Keys)
                                {
                                    client.UpdatePlane(foundPlane);
                                }
                            }
                        }
                    }
                    ))
                {
                    IsBackground = true
                }
                .Start();
            }
            catch (Exception ex)
            {
                throw (ex);
            }
        }

        public void pumpUpdates()
        {
            Random w = new Random();

            List<IAirportCallback> bad = new List<IAirportCallback>();
            int count = 0;
            Node[] initialPlaneLocations = new Node[4];

            initialPlaneLocations[0] = new Node(-1, 527, 36);
            initialPlaneLocations[1] = new Node(-1, 532, 71);
            initialPlaneLocations[2] = new Node(-1, 818, 306);
            initialPlaneLocations[3] = new Node(-1, 863, 261);

            // Create a test Route
            Route testRoute = new Route();
            List<RouteNode> testRouteNodes = new List<RouteNode>();
            testRouteNodes.Add(new RouteNode(0,90,91));
            testRouteNodes.Add(new RouteNode(1,185,79));
            testRouteNodes.Add(new RouteNode(2,280,67));
            testRouteNodes.Add(new RouteNode(3,298,65));
            testRouteNodes.Add(new RouteNode(4,392,52));
            testRouteNodes.Add(new RouteNode(5, 527, 36));
            testRoute.RouteNodes = testRouteNodes;

            while (true)
            {
                Thread.Sleep(w.Next(10000));

                //lock (_clients)
                lock (_planeUpdateClients)
                {
                    //if (_clients.Count > 0)
                    if (_planeUpdateClients.Count > 0)
                    {
                        // This is for testing
                        Flight testFlight = new Flight();
                        string airline = ((count % 2) == 0) ? "SWA" : "VA";
                        testFlight.AirlineCode = airline;
                        testFlight.FlightNumber = count;
                        testFlight.FlightName = airline + count;

                        Plane testPlane = new Plane();
                        testPlane.PlaneId = count;
                        testPlane.Location = initialPlaneLocations[count % 4];
                        testPlane.Heading = 220;
                        //testPlane.CurrentRoute = testRoute;
                        testFlight.FlightId = count;
                        testPlane.PlaneFlight = testFlight;
                        testPlane.UpdateType = PlaneUpdateType.AddNewPlane;

                        // Add new Flight to local collection
                        _activeFlights.Add(testFlight);
                        // Add new Plane to local collection
                        _activePlanes.Add(testPlane);

                        bad.Clear();
                        /*
                        foreach (IAirportCallback client in _clients.Keys)
                        {
                            try
                            {                                
                                client.newFlightUpdate(testFlight);                                
                            }
                            catch (Exception)
                            {
                                Console.WriteLine("AirportService: Calling client.newFlightUpdate failed");
                                bad.Add(client);
                            }
                        }
                        count++;
                        bad.ForEach(bc => _clients.Remove(bc));
                         * */
                        foreach (IAirportCallback client in _planeUpdateClients.Keys)
                        {
                            try
                            {                                
                                client.UpdatePlane(testPlane);
                            }
                            catch (Exception)
                            {
                                Console.WriteLine("AirportService: Calling client.newFlightUpdate failed");
                                bad.Add(client);
                            }
                        }
                        count++;
                        bad.ForEach(bc => _planeUpdateClients.Remove(bc));
                    }
                }
            }
        }

        private Map LoadAirportMapFromFile(string fileName)
        {
            Tuple<double, double> ORIGINAL_MAP_DIM = new Tuple<double, double>(900, 420);
            //Tuple<double, double> SCALED_MAP_DIM = new Tuple<double, double>(1280, 800);
            Tuple<double, double> SCALED_MAP_DIM = new Tuple<double, double>(1056, 493);
            double SCALE_X = SCALED_MAP_DIM.Item1 / ORIGINAL_MAP_DIM.Item1;
            double SCALE_Y = SCALED_MAP_DIM.Item2 / ORIGINAL_MAP_DIM.Item2;

            ObservableCollection<Runway> runways = new ObservableCollection<Runway>();
            ObservableCollection<Taxiway> taxiways = new ObservableCollection<Taxiway>();

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(fileName);
            XmlElement xmlRoot = xmlDoc.DocumentElement;
            XmlNodeList xmlRunways = xmlRoot.SelectNodes("/airport/runways/runway");
            XmlNodeList xmlTaxiways = xmlRoot.SelectNodes("/airport/taxiways/taxiway");

            /*
             * <runway direction1="" direction2="">
             *      <maxWeight>
             *      <width>
             *      <arrivals>
             *      <departures>
             *      <labelLocation>
             *      <nodes>
            */
            foreach (XmlNode xmlRunway in xmlRunways)
            {
                Runway newRunway = new Runway();
                newRunway.Direction1 = xmlRunway.Attributes["direction1"].Value;
                newRunway.Direction2 = xmlRunway.Attributes["direction2"].Value;
                newRunway.MaxWeight = Convert.ToInt32(xmlRunway.ChildNodes[0].InnerText);
                newRunway.Width = Convert.ToInt32(xmlRunway.ChildNodes[1].InnerText);

                // Set RunwayStatus
                if (xmlRunway.ChildNodes[2].InnerText == "1")
                {
                    // If both Arrival and Departure are true, set RunwayStatus to ARRIVE_DEPART
                    if (xmlRunway.ChildNodes[3].InnerText == "1")
                    {
                        newRunway.CurrentRunwayStatus = RunwayStatus.ARRIVE_DEPART;
                    }
                    else
                    {
                        newRunway.CurrentRunwayStatus = RunwayStatus.ARRIVE;
                    }
                }
                else
                {
                    newRunway.CurrentRunwayStatus = RunwayStatus.DEPART;
                }
                //newRunway.Arrivals = (xmlRunway.ChildNodes[2].InnerText == "0" ? false : true);
                //newRunway.Departures = (xmlRunway.ChildNodes[3].InnerText == "0" ? false : true);

                newRunway.SetLabelLocation(xmlRunway.ChildNodes[4].InnerText, SCALE_X, SCALE_Y);

                foreach (XmlNode xmlRunwayNode in xmlRunway.ChildNodes[5].ChildNodes)
                {
                    newRunway.AddNode(xmlRunwayNode.InnerText, SCALE_X, SCALE_Y);
                }

                // Set Runway Endpoints
                newRunway.Endpoint1 = newRunway.Nodes.First();
                newRunway.Endpoint2 = newRunway.Nodes.Last();

                runways.Add(newRunway);
            }

            /*
             * <taxiway name="">
             *      <maxWeight>
             *      <width>
             *      <nodes>
            */
            foreach (XmlNode xmlTaxiway in xmlTaxiways)
            {
                Taxiway newTaxiway = new Taxiway();
                newTaxiway.Name = xmlTaxiway.Attributes["name"].Value;
                newTaxiway.MaxWeight = Convert.ToInt32(xmlTaxiway.ChildNodes[0].InnerText);
                newTaxiway.Width = Convert.ToInt32(xmlTaxiway.ChildNodes[1].InnerText);

                foreach (XmlNode xmlTaxiwayNode in xmlTaxiway.ChildNodes[2].ChildNodes)
                {
                    newTaxiway.AddNode(xmlTaxiwayNode.InnerText, SCALE_X, SCALE_Y);
                }

                // Set Taxiway Endpoints
                newTaxiway.Endpoint1 = newTaxiway.Nodes.First();
                newTaxiway.Endpoint2 = newTaxiway.Nodes.Last();

                taxiways.Add(newTaxiway);
            }

            /*
            foreach(Runway runway in runways) {
                Console.WriteLine(runway);
            }

            foreach(Taxiway taxiway in taxiways) {
                Console.WriteLine(taxiway);
            }

            Console.ReadLine();
            */

            return new Map(runways, taxiways, new ObservableCollection<Gate>());
        }
        
    }
    
}
